package com.tianji.learning.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.green.model.v20180509.ImageSyncScanRequest;
import com.aliyuncs.green.model.v20180509.TextScanRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.io.UnsupportedEncodingException;
import java.util.*;

@Slf4j
@Configuration
@EnableConfigurationProperties(AliyunContentSecutiryProperties.class)
public class AliyunContentSecurityTemplate {
    private IAcsClient client;

    private AliyunContentSecutiryProperties greenProperties;

    public AliyunContentSecurityTemplate(AliyunContentSecutiryProperties greenProperties) {
        this.greenProperties = greenProperties;
        try {
            IClientProfile profile = DefaultProfile
                    .getProfile("cn-shanghai", greenProperties.getAccessKeyID(), greenProperties.getAccessKeySecret());
            DefaultProfile
                    .addEndpoint("cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
            client = new DefaultAcsClient(profile);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Green配置缺失，请补充!");
        }
    }


    /**
     * 阿里云文本内容检查
     *
     * @param textList 待检测文本集合
     * @throws AuditResult 审核结果
     */
    public AuditResult textAudit(List<String> textList){
        try {
            // 1、构建文本审核请求
            TextScanRequest textScanRequest = buildTextScanRequest(textList);

            // 2、发起请求
            HttpResponse httpResponse = client.doAction(textScanRequest);

            // 3、解析审核结果
            return parseTextAuditResult(httpResponse);
        } catch (Exception e) {
            log.error("阿里云文本内容审核出错！");
            return AuditResult.of("review");
        }
    }

    /**
     * 构建文本审核请求对象
     * @param textList 待审核文本集合
     * @return
     * @throws UnsupportedEncodingException
     */
    private static TextScanRequest buildTextScanRequest(List<String> textList) throws UnsupportedEncodingException {
        TextScanRequest textScanRequest = new TextScanRequest();
        textScanRequest.setAcceptFormat(FormatType.JSON);
        textScanRequest.setHttpContentType(FormatType.JSON);
        textScanRequest.setMethod(MethodType.POST); // 指定请求方法
        textScanRequest.setEncoding("UTF-8");
        textScanRequest.setRegionId("cn-shanghai");

        List<Map<String, Object>> tasks = new ArrayList<>();
        for (String text : textList) {
            Map<String, Object> task = new LinkedHashMap<>();
            task.put("dataId", UUID.randomUUID().toString());
            task.put("content", text);  // 待检测的文本，长度不超过10000个字符
            tasks.add(task);
        }

        JSONObject data = new JSONObject();
        data.put("scenes", Arrays.asList("antispam"));  // 检测场景，文本垃圾检测传递：antispam
        data.put("tasks", tasks);
        log.info("文本检测任务：{}", JSON.toJSONString(data, true));
        textScanRequest.setHttpContent(data.toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
        // 请务必设置超时时间
        textScanRequest.setConnectTimeout(3000);
        textScanRequest.setReadTimeout(6000);
        return textScanRequest;
    }

    /**
     * 解析文本审核结果
     * @param httpResponse
     * @return
     */
    @NotNull
    private AuditResult parseTextAuditResult(HttpResponse httpResponse) {
        List<AuditResult> results = new ArrayList<>();
        try {
            if (!httpResponse.isSuccess()) {
                log.error("阿里云文本内容审核出错！");
                return AuditResult.of("review");
            }
            JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
            log.info("文本检测结果：{}", JSON.toJSONString(scrResponse, true));
            if (200 != scrResponse.getInteger("code")) {
                log.error("阿里云文本内容审核出错！");
                return AuditResult.of("review");
            }
            JSONArray taskResults = scrResponse.getJSONArray("data");
            for (Object taskResult : taskResults) {
                if (200 != ((JSONObject) taskResult).getInteger("code")) {
                    log.error("阿里云文本内容审核出错！");
                    return AuditResult.of("review");
                }
                JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
                String content = ((JSONObject) taskResult).getString("content");
                for (Object sceneResult : sceneResults) {
                    String scene = ((JSONObject) sceneResult).getString("scene");
                    String suggestion = ((JSONObject) sceneResult).getString("suggestion");
                    AuditResult auditResult = AuditResult.of(scene, suggestion);
                    auditResult.setText(content);
                    if ("block".equals(suggestion)) {
                        String label = ((JSONObject) sceneResult).getString("label");
                        String reason = this.getTextAuditReason(label);
                        auditResult.setLabel(label);
                        auditResult.setReason(reason);
                    }
                    results.add(auditResult);
                }
            }
        } catch (Exception e) {
            log.error("阿里云文本内容审核出错！", e);
            return AuditResult.of("review");
        }
        return disposeAuditResults(results);
    }

    @NotNull
    private static AuditResult disposeAuditResults(List<AuditResult> results) {
        // 1、日志输出审核结果
        StringBuilder s = new StringBuilder("审核结果: \n");
        results.forEach(r -> s.append(r.toString() + "\n"));
        log.warn(s.toString());

        // 2、检测是否存在违规审核结果，如果有直接返回第一个
        for (AuditResult result : results) {
            if(result.isBlock()){
                return result;
            }
        }

        // 3、检测是否存在不确定审核结果，如果有直接返回第一个
        for (AuditResult result : results) {
            if(result.isReview()){
                return result;
            }
        }

        // 4、返回通过
        return AuditResult.of("pass");
    }

    /**
     * 阿里云图片内容安全
     * @param imageList 待审核图片集合
     */
    public AuditResult imageAudit(List<String> imageList) throws Exception {
        // 1、构建图片审核请求
        ImageSyncScanRequest imageSyncScanRequest = buildImageSyncScanRequest(imageList);

        // 2、发起图片审核请求
        HttpResponse httpResponse = client.doAction(imageSyncScanRequest);

        // 3、解析图片审核结果
        return parseImageAuditResult(httpResponse);
    }

    /**
     * 构建图片审核请求对象
     * @param imageList
     * @return
     */
    private ImageSyncScanRequest buildImageSyncScanRequest(List<String> imageList) {
        ImageSyncScanRequest imageSyncScanRequest = new ImageSyncScanRequest();
        // 指定api返回格式
        imageSyncScanRequest.setAcceptFormat(FormatType.JSON);
        // 指定请求方法
        imageSyncScanRequest.setMethod(MethodType.POST);
        imageSyncScanRequest.setEncoding("utf-8");
        //支持http和https
        imageSyncScanRequest.setProtocol(ProtocolType.HTTP);
        JSONObject httpBody = new JSONObject();

        /**
         * 设置要检测的场景, 计费是按照该处传递的场景进行
         * 一次请求中可以同时检测多张图片，每张图片可以同时检测多个风险场景，计费按照场景计算
         * 例如：检测2张图片，场景传递porn、terrorism，计费会按照2张图片鉴黄，2张图片暴恐检测计算
         * porn: porn表示色情场景检测
         */
        httpBody.put("scenes", Arrays.asList(greenProperties.getScenes().split(",")));

        /**
         * 如果您要检测的文件存于本地服务器上，可以通过下述代码片生成url
         * 再将返回的url作为图片地址传递到服务端进行检测
         */
        /**
         * 设置待检测图片， 一张图片一个task
         * 多张图片同时检测时，处理的时间由最后一个处理完的图片决定
         * 通常情况下批量检测的平均rt比单张检测的要长, 一次批量提交的图片数越多，rt被拉长的概率越高
         * 这里以单张图片检测作为示例, 如果是批量图片检测，请自行构建多个task
         */
        List tasks = new ArrayList();
        for (String imageUrl : imageList) {
            JSONObject task = new JSONObject();
            task.put("dataId", UUID.randomUUID().toString());
            // 设置图片链接。
            task.put("url", imageUrl);
            task.put("time", new Date());
            tasks.add(task);
        }

        httpBody.put("tasks", tasks);
        log.info("图片检测任务：{}", JSON.toJSONString(httpBody, true));
        imageSyncScanRequest.setHttpContent(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(httpBody.toJSONString()),
                "UTF-8", FormatType.JSON);
        /**
         * 请设置超时时间, 服务端全链路处理超时时间为10秒，请做相应设置
         * 如果您设置的ReadTimeout小于服务端处理的时间，程序中会获得一个read timeout异常
         */
        imageSyncScanRequest.setConnectTimeout(3000);
        imageSyncScanRequest.setReadTimeout(10000);
        return imageSyncScanRequest;
    }

    /**
     * 解析图片审核结果
     * @param httpResponse
     * @return
     */
    @Nullable
    private AuditResult parseImageAuditResult(HttpResponse httpResponse) {
        //服务端接收到请求，并完成处理返回的结果
        List<AuditResult> results = new ArrayList<>();
        if (httpResponse != null && httpResponse.isSuccess()) {
            JSONObject scrResponse = JSON.parseObject(org.apache.commons.codec.binary.StringUtils.newStringUtf8(httpResponse.getHttpContent()));
            log.info("图片检测结果：{}", JSON.toJSONString(scrResponse, true));
            //System.out.println(JSON.toJSONString(scrResponse, true));
            int requestCode = scrResponse.getIntValue("code");
            //每一张图片的检测结果
            JSONArray taskResults = scrResponse.getJSONArray("data");
            if (200 == requestCode) {
                for (Object taskResult : taskResults) {
                    //单张图片的处理结果
                    int taskCode = ((JSONObject) taskResult).getIntValue("code");
                    //图片要检测的场景的处理结果, 如果是多个场景，则会有每个场景的结果
                    if (200 == taskCode) {
                        String url = ((JSONObject) taskResult).getString("url");
                        JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
                        for (Object sceneResult : sceneResults) {
                            String scene = ((JSONObject) sceneResult).getString("scene");
                            String suggestion = ((JSONObject) sceneResult).getString("suggestion");
                            AuditResult auditResult = AuditResult.of(scene, suggestion);
                            auditResult.setUrl(url);
                            if("block".equals(suggestion)){
                                String label = ((JSONObject) sceneResult).getString("label");
                                String reason = this.getImageAuditReason(label);
                                auditResult.setLabel(label);
                                auditResult.setReason(reason);
                            }
                            results.add(auditResult);
                        }
                    } else {
                        //单张图片处理失败, 原因视具体的情况详细分析
                        log.error("task process fail. task response:" + JSON.toJSONString(taskResult));
                        return AuditResult.of("review");
                    }
                }
            } else {
                /**
                 * 表明请求整体处理失败，原因视具体的情况详细分析
                 */
                log.error("the whole image scan request failed. response:" + JSON.toJSONString(scrResponse));
                return AuditResult.of("review");
            }
        }
        return disposeAuditResults(results);
    }



    private String getImageAuditReason(String label) {
        String reason = "";
        switch (label) {
            case "normal":
                reason = "正常";
                break;
            case "sexy":
                reason = "性感";
                break;
            case "porn":
                reason = "色情";
                break;
            case "bloody":
                reason = "血腥";
                break;
            case "explosion":
                reason = "爆炸烟光";
                break;
            case "outfit":
                reason = "特殊装束";
                break;
            case "logo":
                reason = "特殊标识";
                break;
            case "weapon":
                reason = "武器";
                break;
            case "politics":
                reason = "涉政";
                break;
            case "violence":
                reason = "打斗";
                break;
            case "crowd":
                reason = "聚众";
                break;
            case "parade":
                reason = "游行";
                break;
            case "carcrash":
                reason = "车祸现场";
                break;
            case "flag":
                reason = "旗帜";
                break;
            case "location":
                reason = "地标";
                break;
            case "drug":
                reason = "涉毒";
                break;
            case "gamble":
                reason = "赌博";
                break;
            case "others":
                reason = "其他";
                break;
            case "ad":
                reason = "其他广告";
                break;
            case "abuse":
                reason = "文字含辱骂内容";
                break;
            case "terrorism":
                reason = "文字含暴恐内容";
                break;
            case "contraband":
                reason = "文字含违禁内容";
                break;
            case "spam":
                reason = "文字含其他垃圾内容";
                break;
            case "npx":
                reason = "牛皮癣广告";
                break;
            case "qrcode":
                reason = "含二维码";
                break;
            case "programCode":
                reason = "含小程序码";
                break;
            case "meaningless":
                reason = "图片中无内容（例如黑屏、白屏）";
                break;
            case "PIP":
                reason = "画中画";
                break;
            case "smoking":
                reason = "吸烟";
                break;
            case "drivelive":
                reason = "车内直播";
                break;
            case "TV":
                reason = "含受管控的logo";
                break;
            case "trademark":
                reason = "含商标";
                break;
            default:
                reason = "未知原因";
                break;
        }
        return reason;
    }

    private String getTextAuditReason(String label) {
        String reason = "";
        switch (label) {
            case "spam":
                reason = "文本内容中含垃圾信息";
                break;
            case "ad":
                reason = "文本内容中含有广告信息";
                break;
            case "politics":
                reason = "文本内容中含有涉政信息";
                break;
            case "terrorism":
                reason = "文本内容中含有暴恐信息";
                break;
            case "abuse":
                reason = "文本内容中含有辱骂信息";
                break;
            case "porn":
                reason = "文本内容中含有色情信息";
                break;
            case "flood":
                reason = "文本内容含有灌水信息";
                break;
            case "contraband":
                reason = "文本内容含有违禁信息";
                break;
            case "meaningless":
                reason = "文本内容无意义";
                break;
            case "negativeScenario":
                reason = "文本内容含不良场景（支持拜金炫富、追星应援、负面情绪、负面诱导等检测场景）";
                break;
            default:
                reason = "未知原因";
                break;
        }
        return reason;
    }
}